package com.springboot.frame.mapper.provider;

import com.springboot.frame.Condition;
import com.springboot.frame.schema.Column;
import com.springboot.frame.schema.PrimaryKey;
import com.springboot.frame.schema.Table;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.ibatis.builder.annotation.ProviderContext;
import org.apache.ibatis.jdbc.SQL;
import java.lang.reflect.Field;
import java.util.Set;
import static org.springframework.util.Assert.notNull;

/**
 * SQL处理类
 *
 * @author liheng
 */
public final class UpdateMapperProvider extends AbstractMapperProvider {

    public String insert(Object parameterObject, ProviderContext providerContext) throws Exception {
        notNull(parameterObject, "parameterObject should not be null on insert");
        Table table = getTable(providerContext);
        SQL sql = new SQL().INSERT_INTO(table.getTableName());
        StringBuilder intoColumns = new StringBuilder();
        primaryKeyHandlerForInsert(table, parameterObject, sql, intoColumns);
        Set<Column> infoSet = table.getColumns();
        if (CollectionUtils.isNotEmpty(infoSet)) {
            for (Column column : infoSet) {
                if (column.isInsertAble() && column.getField().get(parameterObject) != null) {
                    intoColumns.append(column.getColumnName()).append(",");
                    sql.INTO_VALUES("#{" + column.getPropertyName() + column.getJdbcType() + "}");
                }
            }
        }
        if (intoColumns.length() != 0) {
            sql.INTO_COLUMNS(intoColumns.deleteCharAt(intoColumns.length() - 1).toString());
        }
        return sql.toString();
    }


    public String insertAllColumns(Object parameterObject, ProviderContext providerContext)
            throws Exception {
        notNull(parameterObject, "parameterObject should not be null on insertAllColumns");
        Table table = getTable(providerContext);
        SQL sql = new SQL().INSERT_INTO(table.getTableName());
        StringBuilder intoColumns = new StringBuilder();
        primaryKeyHandlerForInsert(table, parameterObject, sql, intoColumns);
        Set<Column> infoSet = table.getColumns();
        if (CollectionUtils.isNotEmpty(infoSet)) {
            for (Column column : infoSet) {
                if (column.isInsertAble() && (column.isNullAble()
                        || column.getField().get(parameterObject) != null)) {
                    intoColumns.append(column.getColumnName()).append(",");
                    sql.INTO_VALUES("#{" + column.getPropertyName() + column.getJdbcType() + "}");
                }
            }
        }
        sql.INTO_COLUMNS(intoColumns.deleteCharAt(intoColumns.length() - 1).toString());
        return sql.toString();
    }

    public String updateById(Object parameterObject, ProviderContext providerContext)
            throws Exception {
        notNull(parameterObject, "parameterObject should not be null on updateById");
        Table table = getTable(providerContext);
        SQL sql = new SQL().UPDATE(table.getTableName());
        primaryKeyHandlerForUpdate(table, parameterObject, sql);
        Set<Column> infoSet = table.getColumns();
        if (CollectionUtils.isNotEmpty(infoSet)) {
            for (Column column : infoSet) {
                if (column.isUpdateAble() && column.getField().get(parameterObject) != null) {
                    sql.SET(column.getColumnName() + "=#{" + column.getPropertyName() + "}");
                }
            }
        }
        return sql.toString();
    }

    public String updateAllColumnsById(Object parameterObject, ProviderContext providerContext)
            throws Exception {
        notNull(parameterObject, "parameterObject should not be null on updateAllColumnsById");
        Table table = getTable(providerContext);
        SQL sql = new SQL().UPDATE(table.getTableName());
        primaryKeyHandlerForUpdate(table, parameterObject, sql);
        Set<Column> infoSet = table.getColumns();
        if (CollectionUtils.isNotEmpty(infoSet)) {
            for (Column mappingField : infoSet) {
                if (mappingField.isUpdateAble() && (mappingField.isNullAble()
                        || mappingField.getField().get(parameterObject) != null)) {
                    sql.SET(
                            mappingField.getColumnName() + "=#{" + mappingField.getPropertyName() + mappingField
                                    .getJdbcType() + "}");
                }
            }
        }
        return sql.toString();
    }

    public String update(Condition<?> condition, ProviderContext providerContext) throws Exception {
        notNull(condition, "condition should not be null on updateByCondition");
        Table table = getTable(providerContext);
        Object parameterObject = condition.getParameterObject();
        SQL sql = new SQL().UPDATE(table.getTableName());
        if (parameterObject != null) {
            PrimaryKey primaryKey = table.getPrimaryKey();
            if (primaryKey != null && primaryKey.getField().get(parameterObject) != null) {
                sql.WHERE(
                        primaryKey.getColumnName() + " = #{parameterObject." + primaryKey.getPropertyName()
                                + "}");
            }
            for (Column mappingField : table.getColumns()) {
                if (mappingField.isUpdateAble()) {
                    if (mappingField.getField().get(parameterObject) != null) {
                        sql.SET(
                                mappingField.getColumnName() + "=#{parameterObject." + mappingField
                                        .getPropertyName() + mappingField
                                        .getJdbcType() + "}");
                    }
                }
            }
        }
        sql.WHERE(condition.getWhere().toArray(new String[condition.getWhere().size()]));
        return sql.toString();
    }

    public String updateAllColumns(Condition<?> condition, ProviderContext providerContext)
            throws Exception {
        notNull(condition, "condition should not be null on updateByCondition");
        Table table = getTable(providerContext);
        Object parameterObject = condition.getParameterObject();
        SQL sql = new SQL().UPDATE(table.getTableName());
        if (parameterObject != null) {
            PrimaryKey primaryKey = table.getPrimaryKey();
            if (primaryKey != null && primaryKey.getField().get(parameterObject) != null) {
                sql.WHERE(
                        primaryKey.getColumnName() + " = #{parameterObject." + primaryKey.getPropertyName()
                                + "}");
            }
            for (Column mappingField : table.getColumns()) {
                if (mappingField.isUpdateAble() && (mappingField.isNullAble()
                        || mappingField.getField().get(parameterObject) != null)) {
                    if (mappingField.getField().get(parameterObject) != null) {
                        sql.SET(
                                mappingField.getColumnName() + "=#{parameterObject." + mappingField
                                        .getPropertyName() + mappingField
                                        .getJdbcType() + "}");
                    }
                }
            }
        }
        sql.WHERE(condition.getWhere().toArray(new String[condition.getWhere().size()]));
        return sql.toString();
    }

  /**
   * 保存或更新
   * @param parameterObject
   * @param providerContext
   * @return
   * @throws Exception
   */
    public String saveOrUpdate(Object parameterObject, ProviderContext providerContext)
            throws Exception {
        notNull(parameterObject, "parameterObject should not be null on saveOrUpdate");
        Table table = getTable(providerContext);
        if (table.getPrimaryKey().getField().get(parameterObject) == null) {
            return insert(parameterObject, providerContext);
        } else {
            return updateById(parameterObject, providerContext);
        }
    }

    public String saveOrUpdateAllColumns(Object parameterObject, ProviderContext providerContext)
            throws Exception {
        notNull(parameterObject, "parameterObject should not be null on saveOrUpdateAllColumns");
        Table table = getTable(providerContext);
        if (table.getPrimaryKey().getField().get(parameterObject) == null) {
            return insertAllColumns(parameterObject, providerContext);
        } else {
            return updateAllColumnsById(parameterObject, providerContext);
        }
    }

    private void primaryKeyHandlerForInsert(Table info, Object parameterObject, SQL sql,
                                            StringBuilder intoColumns)
            throws Exception {
        PrimaryKey primaryKey = info.getPrimaryKey();
        if (primaryKey != null) {
            Field field = primaryKey.getField();
            if (field.get(parameterObject) != null) {
                intoColumns.append(primaryKey.getColumnName()).append(",");
                sql.INTO_VALUES("#{" + primaryKey.getPropertyName() + "}");
            } else {
                switch (primaryKey.getGenerationType()) {
                    case SEQUENCE:
                        intoColumns.append(primaryKey.getColumnName()).append(",");
                        sql.INTO_VALUES(primaryKey.getSequenceGenerator().sequenceName() + ".Nextval");
                        break;
                    case AUTO:
                        field.set(parameterObject, primaryKey.getAutoKeyGenerator().generateId());
                        intoColumns.append(primaryKey.getColumnName()).append(",");
                        sql.INTO_VALUES("#{" + primaryKey.getPropertyName() + "}");
                        break;
                    default:
                        break;
                }
            }
        }
    }

    private void primaryKeyHandlerForUpdate(Table info, Object parameterObject, SQL sql)
            throws Exception {
        PrimaryKey primaryKey = info.getPrimaryKey();
        if (primaryKey != null) {
            if (primaryKey.getField().get(parameterObject) == null) {
                sql.WHERE(primaryKey.getColumnName() + " IS NULL");
            } else {
                sql.WHERE(primaryKey.getColumnName() + " = #{" + primaryKey.getPropertyName() + "}");
            }
        }
    }
}
